package com.kitten.search.biz.service.impl;

import cn.hutool.core.collection.CollUtil;
import com.kitten.framework.common.constants.DateConstants;
import com.kitten.framework.common.response.PageResponse;
import com.kitten.framework.common.response.Response;
import com.kitten.framework.common.utils.DateUtils;
import com.kitten.framework.common.utils.NumberUtils;
import com.kitten.search.api.dto.RebuildNoteDocumentReqDTO;
import com.kitten.search.api.dto.RebuildUserDocumentReqDTO;
import com.kitten.search.biz.domain.mapper.SelectMapper;
import com.kitten.search.biz.enums.NotePublishTimeRangeEnum;
import com.kitten.search.biz.enums.NoteSortTypeEnum;
import com.kitten.search.biz.index.NoteIndex;
import com.kitten.search.biz.index.UserIndex;
import com.kitten.search.biz.model.vo.SearchNoteReqVO;
import com.kitten.search.biz.model.vo.SearchNoteRspVO;
import com.kitten.search.biz.model.vo.SearchUserReqVO;
import com.kitten.search.biz.model.vo.SearchUserRspVO;
import com.kitten.search.biz.service.SearchService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.assertj.core.util.Lists;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.lucene.search.function.FieldValueFactorFunction;
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FieldValueFactorFunctionBuilder;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @author kitten
 */
@Slf4j
@Service
public class SearchServiceImpl implements SearchService {

    @Resource
    private RestHighLevelClient restHighLevelClient;
    @Resource
    private SelectMapper selectMapper;

    @Override
    public PageResponse<SearchUserRspVO> searchUser(SearchUserReqVO searchUserReqVO) {

        String keyword = searchUserReqVO.getKeyword();
        Integer pageNo = searchUserReqVO.getPageNo();
        // 构建SearchRequest, 指定索引
        SearchRequest searchRequest = new SearchRequest(UserIndex.NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();    // 查询内容
        // 指定查询条件
        searchSourceBuilder.query(QueryBuilders.multiMatchQuery(
                keyword, UserIndex.FIELD_USER_NICKNAME, UserIndex.FIELD_USER_KITTENBOOK_ID
        ));
        // 排序, 按照用户粉丝数倒序
        SortBuilder<?> sortBuilder = new FieldSortBuilder(UserIndex.FIELD_USER_FANS_TOTAL).order(SortOrder.DESC);
        searchSourceBuilder.sort(sortBuilder);
        // 分页, 每页10条 (设置分页，from 和 size
        int pageSize = 10; // 每页展示数据量
        int from = (pageNo - 1) * pageSize; // 偏移量

        searchSourceBuilder.from(from);
        searchSourceBuilder.size(pageSize);
        // 设置高亮
        searchSourceBuilder.highlighter(new HighlightBuilder().field(UserIndex.FIELD_USER_NICKNAME).preTags("</strong>").postTags("</strong>"));
        // 设置查询内容
        searchRequest.source(searchSourceBuilder);
        // 返参
        List<SearchUserRspVO> searchUserRspVOS = null;
        // 总文档数
        long total = 0;


        try {
            log.info("searchRequest: {}", searchRequest);
            // 执行查询
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            // 解析结果
            total = searchResponse.getHits().getTotalHits().value;
            log.info("===> 命中文档数  hits.total: {}", total);

            searchUserRspVOS = Lists.newArrayList();
            // 获取命中的文档
            SearchHits hits = searchResponse.getHits();

            for (SearchHit hit : hits) {
                log.info("hit: {}", hit.getSourceAsString());
                // 解析文档, 所有字段
                Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                // 高亮
                String highlightedNickname = null;
                if (CollUtil.isNotEmpty(hit.getHighlightFields())
                        && hit.getHighlightFields().containsKey(UserIndex.FIELD_USER_NICKNAME)) {
                    highlightedNickname = hit.getHighlightFields().get(UserIndex.FIELD_USER_NICKNAME).getFragments()[0].toString();
                }

                // 提取
                Long userId = ((Number) sourceAsMap.get(UserIndex.FIELD_USER_ID)).longValue();
                String nickname = (String) sourceAsMap.get(UserIndex.FIELD_USER_NICKNAME);
                String avatar = (String) sourceAsMap.get(UserIndex.FIELD_USER_AVATAR);
                String kittenbookId = (String) sourceAsMap.get(UserIndex.FIELD_USER_KITTENBOOK_ID);
                Integer noteTotal = (Integer) sourceAsMap.get(UserIndex.FIELD_USER_NOTE_TOTAL);
                Integer fansTotal = (Integer) sourceAsMap.get(UserIndex.FIELD_USER_FANS_TOTAL);


                // 封装返回对象
                SearchUserRspVO searchUserRspVO = SearchUserRspVO.builder()
                        .userId(userId)
                        .nickname(nickname)
                        .avatar(avatar)
                        .kittenbookId(kittenbookId)
                        .noteTotal(noteTotal)
                        .fansTotal(NumberUtils.formatNumberString(fansTotal))
                        .highlight(highlightedNickname)
                        .build();
                searchUserRspVOS.add(searchUserRspVO);
            }

        } catch (Exception e) {
            log.error("===> ES search user error", e);
        }

        return PageResponse.success(searchUserRspVOS, pageNo, total);
    }

    /**
     * 搜索笔记
     * @param searchNoteReqVO
     * @return
     */
    @Override
    public PageResponse<SearchNoteRspVO> searchNote(SearchNoteReqVO searchNoteReqVO) {
        String keyword = searchNoteReqVO.getKeyword();
        Integer pageNo = searchNoteReqVO.getPageNo();
        Integer type = searchNoteReqVO.getType();
        Integer publishTimeRange = searchNoteReqVO.getPublishTimeRange();
        // 构建SearchRequest, 指定索引
        SearchRequest searchRequest = new SearchRequest(NoteIndex.NAME);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();    // 查询内容

        // 创建查询条件, 指定权重 (不指定时默认1.0)
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery().must(
                QueryBuilders.multiMatchQuery(keyword)
                        .field(NoteIndex.FIELD_NOTE_TITLE, 2.0f)
                        .field(NoteIndex.FIELD_NOTE_TOPIC)
        );
        if (Objects.nonNull(type)) {    // 若勾选了类型, 则添加类型过滤
            boolQueryBuilder.filter(QueryBuilders.termQuery(NoteIndex.FIELD_NOTE_TYPE, type));
        }
        NotePublishTimeRangeEnum notePublishTimeRangeEnum = NotePublishTimeRangeEnum.valueOf(publishTimeRange);
        if (Objects.nonNull(notePublishTimeRangeEnum)) {
            String startTime = null;
            String endTime = LocalDateTime.now().format(DateConstants.DATE_FORMAT_Y_M_D_H_M_S);
            switch (notePublishTimeRangeEnum) {
                case DAY -> {
                    startTime = DateUtils.localDateTime2String(LocalDateTime.now().minusDays(1));
                }
                case WEEK -> {
                    startTime = DateUtils.localDateTime2String(LocalDateTime.now().minusWeeks(1));
                }
                case HALF_YEAR -> {
                    startTime = DateUtils.localDateTime2String(LocalDateTime.now().minusMonths(6));
                }
            }
            if (StringUtils.isNotBlank(startTime)) {
                boolQueryBuilder.filter(QueryBuilders.rangeQuery(NoteIndex.FIELD_NOTE_UPDATE_TIME)
                        .gte(startTime)
                        .lte(endTime)
                );
            }
        }
        // 设置排序
        NoteSortTypeEnum noteSortTypeEnum = NoteSortTypeEnum.valueOf(searchNoteReqVO.getSort());
        if (Objects.nonNull(noteSortTypeEnum)) {
            switch (noteSortTypeEnum) {
                case LATEST -> {    // 最新发布
                    searchSourceBuilder.sort(new FieldSortBuilder(NoteIndex.FIELD_NOTE_UPDATE_TIME).order(SortOrder.DESC));
                }
                case MOST_LIKE -> {    // 最多点赞
                    searchSourceBuilder.sort(new FieldSortBuilder(NoteIndex.FIELD_NOTE_LIKE_TOTAL).order(SortOrder.DESC));
                }
                case MOST_COMMENT -> {    // 最多评论
                    searchSourceBuilder.sort(new FieldSortBuilder(NoteIndex.FIELD_NOTE_COMMENT_TOTAL).order(SortOrder.DESC));
                }
                case MOST_COLLECT -> {    // 最多收藏
                    searchSourceBuilder.sort(new FieldSortBuilder(NoteIndex.FIELD_NOTE_COLLECT_TOTAL).order(SortOrder.DESC));
                }
            }
            searchSourceBuilder.query(boolQueryBuilder);
        }else {     // 综合排序
            searchSourceBuilder.sort(new FieldSortBuilder("_score").order(SortOrder.DESC));
            FunctionScoreQueryBuilder.FilterFunctionBuilder[] filterFunctionBuilders = new FunctionScoreQueryBuilder.FilterFunctionBuilder[] {
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                            new FieldValueFactorFunctionBuilder(NoteIndex.FIELD_NOTE_LIKE_TOTAL)
                                    .factor(0.5f)
                                    .modifier(FieldValueFactorFunction.Modifier.SQRT)
                                    .missing(0.0f)
                    ),
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                            new FieldValueFactorFunctionBuilder(NoteIndex.FIELD_NOTE_COLLECT_TOTAL)
                                    .factor(0.3f)
                                    .modifier(FieldValueFactorFunction.Modifier.SQRT)
                                    .missing(0.0f)
                    ),
                    new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                            new FieldValueFactorFunctionBuilder(NoteIndex.FIELD_NOTE_COMMENT_TOTAL)
                                    .factor(0.2f)
                                    .modifier(FieldValueFactorFunction.Modifier.SQRT)
                                    .missing(0.0f)
                    ),
            };
            FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(boolQueryBuilder, filterFunctionBuilders)
                    .scoreMode(FunctionScoreQuery.ScoreMode.SUM) // score_mode 为 sum
                    .boostMode(CombineFunction.SUM); // boost_mode 为 sum

            // 设置查询
            searchSourceBuilder.query(functionScoreQueryBuilder);
        }

        // 执行查询
        int pageSize = 10; // 每页展示数据量
        int from = (pageNo - 1) * pageSize; // 偏移量
        searchSourceBuilder.from(from);
        searchSourceBuilder.size(pageSize);

        // 设置高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field(NoteIndex.FIELD_NOTE_TITLE)
                .preTags("<strong>")
                .postTags("</strong>");
        searchSourceBuilder.highlighter(highlightBuilder);

        searchRequest.source(searchSourceBuilder);

        // 返参 VO集合
        List<SearchNoteRspVO> searchNoteRspVOS = null;
        // 总文档数
        long total = 0;

        try {
            log.info("searchRequest: {}", searchRequest);
            // 执行查询
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
            // 解析结果
            total = searchResponse.getHits().getTotalHits().value;
            log.info("===> 命中文档数  hits.total: {}", total);

            searchNoteRspVOS = Lists.newArrayList();
            // 获取命中的文档
            SearchHits hits = searchResponse.getHits();

            for(SearchHit hit : hits) {
                log.info("命中文档: {}", hit.getSourceAsString());
                Map<String, Object> sourceAsMap = hit.getSourceAsMap();
                // 提取字段
                Long noteId = ((Number) sourceAsMap.get(NoteIndex.FIELD_NOTE_ID)).longValue();
                String title = (String) sourceAsMap.get(NoteIndex.FIELD_NOTE_TITLE);
                String cover = (String) sourceAsMap.get(NoteIndex.FIELD_NOTE_COVER);
                String avatar = (String) sourceAsMap.get(NoteIndex.FIELD_NOTE_AVATAR);
                String nickname = (String) sourceAsMap.get(NoteIndex.FIELD_NOTE_NICKNAME);
                String updateTimeStr = (String) sourceAsMap.get(NoteIndex.FIELD_NOTE_UPDATE_TIME);
                LocalDateTime updateDateTime = LocalDateTime.parse(updateTimeStr, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                Integer likeTotal = (Integer) sourceAsMap.get(NoteIndex.FIELD_NOTE_LIKE_TOTAL);
                Integer commentTotal = (Integer) sourceAsMap.get(NoteIndex.FIELD_NOTE_COMMENT_TOTAL);
                Integer collectTotal = (Integer) sourceAsMap.get(NoteIndex.FIELD_NOTE_COLLECT_TOTAL);

                String highlightedTitle = null; // 高亮字段
                if (CollUtil.isNotEmpty(hit.getHighlightFields())
                        && hit.getHighlightFields().containsKey(NoteIndex.FIELD_NOTE_TITLE)) {
                    highlightedTitle = hit.getHighlightFields().get(NoteIndex.FIELD_NOTE_TITLE).getFragments()[0].toString();
                }

                // 构建 VO 实体类
                SearchNoteRspVO searchNoteRspVO = SearchNoteRspVO.builder()
                        .noteId(noteId)
                        .cover(cover)
                        .title(title)
                        .highlightTitle(highlightedTitle)
                        .avatar(avatar)
                        .nickname(nickname)
                        .updateTime(DateUtils.formatRelativeTime(updateDateTime))
                        .likeTotal(NumberUtils.formatNumberString(likeTotal))
                        .commentTotal(NumberUtils.formatNumberString(commentTotal))
                        .collectTotal(NumberUtils.formatNumberString(collectTotal))
                        .build();
                searchNoteRspVOS.add(searchNoteRspVO);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return PageResponse.success(searchNoteRspVOS, pageNo, total);
    }

    /**
     * 重构笔记文档
     * @param rebuildNoteDocumentReqDTO
     * @return
     */
    @Override
    public Response<Long> rebuildNoteDocument(RebuildNoteDocumentReqDTO rebuildNoteDocumentReqDTO) {
        Long noteId = rebuildNoteDocumentReqDTO.getId();

        // 从数据库查询 Elasticsearch 索引数据
        List<Map<String, Object>> result = selectMapper.selectEsNoteIndexData(noteId, null);

        // 遍历查询结果，将每条记录同步到 Elasticsearch
        for (Map<String, Object> recordMap : result) {
            // 创建索引请求对象，指定索引名称
            IndexRequest indexRequest = new IndexRequest(NoteIndex.NAME);
            // 设置文档的 ID，使用记录中的主键 “id” 字段值
            indexRequest.id((String.valueOf(recordMap.get(NoteIndex.FIELD_NOTE_ID))));
            // 设置文档的内容，使用查询结果的记录数据
            indexRequest.source(recordMap);
            // 将数据写入 Elasticsearch 索引
            try {
                restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
            } catch (IOException e) {
                log.error("==> 重建笔记文档失败: ", e);
            }
        }
        return Response.success();
    }


    /**
     * 重构用户文档
     * @param rebuildUserDocumentReqDTO
     * @return
     */
    @Override
    public Response<Long> rebuildUserDocument(RebuildUserDocumentReqDTO rebuildUserDocumentReqDTO) {
        Long userId = rebuildUserDocumentReqDTO.getId();

        // 从数据库查询 Elasticsearch 索引数据
        List<Map<String, Object>> result = selectMapper.selectEsUserIndexData(userId);

        // 遍历查询结果，将每条记录同步到 Elasticsearch
        for (Map<String, Object> recordMap : result) {
            // 创建索引请求对象，指定索引名称
            IndexRequest indexRequest = new IndexRequest(UserIndex.NAME);
            // 设置文档的 ID，使用记录中的主键 “id” 字段值
            indexRequest.id((String.valueOf(recordMap.get(UserIndex.FIELD_USER_ID))));
            // 设置文档的内容，使用查询结果的记录数据
            indexRequest.source(recordMap);
            // 将数据写入 Elasticsearch 索引
            try {
                restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
            } catch (IOException e) {
                log.error("==> 重建用户文档异常: ", e);
            }
        }
        return Response.success();
    }
}
